﻿using System;
using System.Configuration;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
using System.Windows.Forms;

namespace ProxyServerEmulator
{
    public class Server
    {
        static int BufferSize = 8192;
        static char[] semiSplit = new char[] { ';' };
        static char[] equalSplit = new char[] { '=' };
        static String[] colonSpaceSplit = new string[] { ": " };
        static char[] spaceSplit = new char[] { ' ' };
        static char[] commaSplit = new char[] { ',' };
        static Regex cookieRegEx = new Regex(@",(?! )");
        static object outputLockObj = new object();

        static TcpListener Listener;
        Thread Listener_Thread;

        public Server(IPAddress IP, int Port)
        {
            if (Listener == null)
                Listener = new TcpListener(IP, Port);
        }

        public void Start()
        {
            
            Listener.Start();
            
            Listener_Thread = new Thread(new ParameterizedThreadStart(Listen));
            Listener_Thread.IsBackground = true;
            Listener_Thread.Start(Listener);
        }

        /// <summary>
        /// Stop TcpListener and all components
        /// </summary>
        public void Stop()
        {
            Listener.Stop();
            if (Listener_Thread != null)
            {
                Listener_Thread.Abort();
                Listener_Thread.Join();
                Listener_Thread.Join();
            }
        }

        /// <summary>
        /// Listen to the TcpClient
        /// </summary>
        /// <param name="obj"></param>
        private void Listen(Object obj)
        {
            try
            {
                while (true)
                {

                    TcpClient client = Listener.AcceptTcpClient();
                    while (!ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessClient), client)) ;
                }
            }
            catch (ThreadAbortException) { }
            catch (SocketException) { }
        }

        /// <summary>
        /// Process client when connected
        /// </summary>
        /// <param name="obj"></param>
        private static void ProcessClient(Object obj)
        {
            TcpClient client = (TcpClient)obj;
            try
            {
                HTTPProcess(client);
            }
            catch { }
            finally
            {
                client.Close();
            }
        }

        /// <summary>
        /// Return the list the banned sites
        /// </summary>
        /// <returns></returns>

        static List<String> BannedList()
        {
            
            List<String> List = new List<String>();
            XmlTextReader reader = new XmlTextReader("Banned.xml");
            XmlDataDocument doc = new XmlDataDocument();
            doc.Load(reader);

            XmlNodeList sites = doc.SelectNodes("BANNEDSITES");
            for (int i = 0; i < sites[0].ChildNodes.Count; i++)
            {
                List.Add(sites[0].ChildNodes[i].ChildNodes[1].InnerText);
            }

            reader.Close();
            return List;
        }


    /// <summary>
    /// Process HTTP Header to get the web response
    /// </summary>
    /// <param name="client"></param>
    private static void HTTPProcess(TcpClient client)
    {
        Stream clientStream = client.GetStream();
        Stream outStream = clientStream;
        StreamReader clientStreamReader = new StreamReader(clientStream);
        CacheEntry cacheEntry = null;
        MemoryStream cacheStream = null;
        List<String> BannedSites = BannedList();

        try
        {
            //read the first line HTTP command
            String httpCmd = clientStreamReader.ReadLine();
            if (String.IsNullOrEmpty(httpCmd))
            {
                clientStreamReader.Close();
                clientStream.Close();
                return;
            }
            //break up the line into three components
            String[] splitBuffer = httpCmd.Split(spaceSplit, 3);

            String method = splitBuffer[0];
            String remoteUri = splitBuffer[1];
            Version version = new Version(1, 0);

            HttpWebRequest webReq;
            HttpWebResponse response = null;


            //construct the web request that we are going to issue on behalf of the client.
            for (int i = 0; i < BannedSites.Count(); i++)
            {
                if (remoteUri.IndexOf(BannedSites[i]) > 0)
                {
                    MessageBox.Show("Website nay da bi chan");
                        return;
                }
            }
            webReq = (HttpWebRequest)HttpWebRequest.Create(remoteUri);
            webReq.Method = method;
            webReq.ProtocolVersion = version;

            //read the request headers from the client and copy them to our request
            int contentLen = ReadRequestHeaders(clientStreamReader, webReq);
            webReq.Proxy = null;
            webReq.KeepAlive = false;
            webReq.AllowAutoRedirect = false;
            webReq.AutomaticDecompression = DecompressionMethods.None;


            // Using Compledted Request From Cache
            if (method.ToUpper() == "GET")
                cacheEntry = ProxyCache.GetData(webReq);
            else if (method.ToUpper() == "POST")
            {
                char[] postBuffer = new char[contentLen];
                int bytesRead;
                int totalBytesRead = 0;
                StreamWriter sw = new StreamWriter(webReq.GetRequestStream());
                while (totalBytesRead < contentLen && (bytesRead = clientStreamReader.ReadBlock(postBuffer, 0, contentLen)) > 0)
                {
                    totalBytesRead += bytesRead;
                    sw.Write(postBuffer, 0, bytesRead);
                }

                sw.Close();
            }

            if (cacheEntry == null)
            {
                webReq.Timeout = 15000;

                try
                {
                    response = (HttpWebResponse)webReq.GetResponse();
                }
                catch (WebException webEx)
                {
                    response = webEx.Response as HttpWebResponse;
                }
                if (response != null)
                {
                    List<Tuple<String, String>> responseHeaders = ProcessResponse(response);
                    StreamWriter myResponseWriter = new StreamWriter(outStream);
                    Stream responseStream = response.GetResponseStream();
                    try
                    {
                        // Send the response status and response headers
                        WriteResponseStatus(response.StatusCode, response.StatusDescription, myResponseWriter);
                        WriteResponseHeaders(myResponseWriter, responseHeaders);

                        DateTime? expires = null;
                        CacheEntry entry = null;
                        Boolean canCache = (ProxyCache.CanCache(response.Headers, ref expires));
                        if (canCache)
                        {
                            entry = ProxyCache.MakeEntry(webReq, response, responseHeaders, expires);
                            if (response.ContentLength > 0)
                                cacheStream = new MemoryStream(entry.ResponseBytes);
                        }

                        Byte[] buffer;
                        if (response.ContentLength > 0)
                            buffer = new Byte[response.ContentLength];
                        else
                            buffer = new Byte[BufferSize];

                        int bytesRead;

                        while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            if (cacheStream != null)
                                cacheStream.Write(buffer, 0, bytesRead);
                            outStream.Write(buffer, 0, bytesRead);
                        }

                        responseStream.Close();
                        if (cacheStream != null)
                        {
                            cacheStream.Flush();
                            cacheStream.Close();
                        }

                        outStream.Flush();
                        if (canCache)
                            ProxyCache.AddData(entry);
                    }
                    catch
                    {
                    }
                    finally
                    {
                        responseStream.Close();
                        response.Close();
                        myResponseWriter.Close();
                    }
                }
            }
            else
            {
                StreamWriter myResponseWriter = new StreamWriter(outStream);
                try
                {
                    WriteResponseStatus(cacheEntry.StatusCode, cacheEntry.StatusDescription, myResponseWriter);
                    WriteResponseHeaders(myResponseWriter, cacheEntry.Headers);
                    if (cacheEntry.ResponseBytes != null)
                    {
                        outStream.Write(cacheEntry.ResponseBytes, 0, cacheEntry.ResponseBytes.Length);
                    }
                    myResponseWriter.Close();
                }
                catch
                {
                }
                finally
                {
                    myResponseWriter.Close();
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            clientStreamReader.Close();
            clientStream.Close();
            outStream.Close();
            if (cacheStream != null)
                cacheStream.Close();
        }

    }

        private static List<Tuple<String, String>> ProcessResponse(HttpWebResponse response)
        {
            String value = null;
            String header = null;
            List<Tuple<String, String>> returnHeaders = new List<Tuple<String, String>>();
            foreach (String s in response.Headers.Keys)
            {
                if (s.ToLower() == "set-cookie")
                {
                    header = s;
                    value = response.Headers[s];
                }
                else
                    returnHeaders.Add(new Tuple<String, String>(s, response.Headers[s]));
            }

            if (!String.IsNullOrWhiteSpace(value))
            {
                response.Headers.Remove(header);
                String[] cookies = cookieRegEx.Split(value);
                foreach (String cookie in cookies)
                    returnHeaders.Add(new Tuple<String, String>("Set-Cookie", cookie));

            }
            returnHeaders.Add(new Tuple<String, String>("Proxy Provided by", "Proxy Server Emulator v1.0"));
            return returnHeaders;
        }

        private static void WriteResponseStatus(HttpStatusCode code, String description, StreamWriter myResponseWriter)
        {
            String s = String.Format("HTTP/1.0 {0} {1}", (Int32)code, description);
            myResponseWriter.WriteLine(s);
        }

        private static void WriteResponseHeaders(StreamWriter myResponseWriter, List<Tuple<String, String>> headers)
        {
            if (headers != null)
            {
                foreach (Tuple<String, String> header in headers)
                    myResponseWriter.WriteLine(String.Format("{0}: {1}", header.Item1, header.Item2));
            }
            myResponseWriter.WriteLine();
            myResponseWriter.Flush();
        }

        private static int ReadRequestHeaders(StreamReader sr, HttpWebRequest webReq)
        {
            String httpCmd;
            int contentLen = 0;
            do
            {
                httpCmd = sr.ReadLine();
                if (String.IsNullOrEmpty(httpCmd))
                    return contentLen;
                String[] header = httpCmd.Split(colonSpaceSplit, 2, StringSplitOptions.None);
                switch (header[0].ToLower())
                {
                    case "host":
                        webReq.Host = header[1];
                        break;
                    case "user-agent":
                        webReq.UserAgent = header[1];
                        break;
                    case "accept":
                        webReq.Accept = header[1];
                        break;
                    case "referer":
                        webReq.Referer = header[1];
                        break;
                    case "cookie":
                        webReq.Headers["Cookie"] = header[1];
                        break;
                    case "proxy-connection":
                    case "connection":
                    case "keep-alive":
                        //ignore these
                        break;
                    case "content-length":
                        int.TryParse(header[1], out contentLen);
                        break;
                    case "content-type":
                        webReq.ContentType = header[1];
                        break;
                    case "if-modified-since":
                        String[] sb = header[1].Trim().Split(semiSplit);
                        DateTime d;
                        if (DateTime.TryParse(sb[0], out d))
                            webReq.IfModifiedSince = d;
                        break;
                    default:
                        try
                        {
                            webReq.Headers.Add(header[0], header[1]);
                        }
                        catch 
                        {
                        }
                        break;
                }
            } while (!String.IsNullOrWhiteSpace(httpCmd));
            return contentLen;
        }
    }
}
